<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>俄罗斯方块</title>
		<link rel="shortcut icon" href="img/favicon.png" type="image/x-icon"/>
		<link rel="stylesheet" href="index.css">
		<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
	</head>
	<body>
		<div id="app">
			<!-- 基本布局 -->
			<div class="tetris">
				<img src="img/bg4.jpg" alt="" class="tetris-bg">
				<!-- 棋盘 -->
				<div class="tetris-box">
					<div class="map">
						<div class="tr" v-for="(item1, index1) in list" :key="index1">
							<div class="td" v-for="(item2, index2) in item1" :key="index2">
								<div class="brick brick-1" v-if="item2 == 1 || item2 == 2"></div>
							</div>
						</div>
					</div>
					<!-- 游戏遮罩 -->
					<div class="mask" v-if="!isStart || (isStart && isStop)">按<span>空格</span>开始游戏</div>
				</div>
				
				<!-- 下一个形状 -->
				<div class="tetris-next">
					<div class="tetris-next-tr" v-for="item1 in list_next">
						<div class="tetris-next-td" v-for="item2 in item1">
							<div class="brick brick-1" v-if="item2 == 1"></div>
						</div>
					</div>
				</div>
				
				<!-- 分数 -->
				<div class="tetris-score">{{ score }}</div>
			</div>
		</div>
		<script>
			const { createApp } = Vue;
			createApp({
				data() {
					return {
						list: [], 				// 0=空地 1=有砖块 2=落底砖块
						list_next: [],		// 下一个图形的地图
						row: 18,					// 行
						col: 14,					// 列
						speed: 400,				// 下落速度
						isStart: false, 	// 是否开始
						isOver: false,		// 是否结束
						isStop: true,			// 是否暂停
						brick: {
							x: 0,
							y: 0,
							value: 0,
							style: 'brick-1'
						},
						brickList: [],
						brickNow: [],			// 当前
						nowIndex1: null,	// 当前是哪个图形
						nowIndex2: null,	// 当前图形是哪个形状
						brickNext: [],		// 下一个
						nextIndex1: null,	// 下一个是哪个图形
						nextIndex2: null,	// 下一个图形是哪个形状
						timer: null,
						score: 0,
						scoreList: [
							{ row: 1, score: 10 },
							{ row: 2, score: 30 },
							{ row: 3, score: 60 },
							{ row: 4, score: 100 }
						],
					}
				},
				created() {
					// 监听键盘事件
					window.addEventListener('keydown', (e) => {
						if(e.keyCode === 32) {
							this.onSpace();
						}
						if([37,38,39,40, 87,65,83,68].includes(e.keyCode)) {
							this.onDirection(e.keyCode)
						}
					}, true);
				},
				mounted() {
					this.createMap();
					this.createBrick();
				},
				methods: {
					// 生成地图
					createMap() {
						this.list = [];
						for (let i = 0; i < this.row; i++) {
							this.list[i] = [];
							for(let j = 0; j < this.col; j++) {
								this.list[i][j] = 0;
							}
						}
						for (let i = 0; i < 4; i++) {
							this.list_next[i] = [];
							for(let j = 0; j < 4; j++) {
								this.list_next[i][j] = 0;
							}
						}
						this.initAnimate()
					},
					
					// 初始化地图动画
					async initAnimate() {
						// 画面整体从下到上扫一遍
						for (let i = this.row - 1; i >= 0; i--) {
							await this.sleep(30)
							for(let j = 0; j < this.col; j++) {
								this.list[i][j] = 1;
							}
							this.$forceUpdate()
						}
						// 画面整体从上到下扫一遍
						for (let i = 0; i < this.row; i++) {
							await this.sleep(30)
							for(let j = 0; j < this.col; j++) {
								this.list[i][j] = 0;
							}
							this.$forceUpdate()
						}
						this.isStart = false;
						this.isOver = false;
					},
					
					sleep(time) {
						return new Promise((resolve) => {
							setTimeout(() => {
								resolve()
							}, time)
						})
					},
					
					// 生成形状方块
					createBrick() {
						this.brickList = [
							[
								[[0,1], [1,1], [2,1], [2,2]], 
								[[1,2],	[1,1], [1,0], [2,0]], 
								[[2,1], [1,1], [0,1], [0,0]], 
								[[1,0], [1,1], [1,2], [0,2]],
							],			// L
							[
								[[0,1], [1,1], [2,1], [2,0]], 
								[[1,2], [1,1], [1,0], [0,0]], 
								[[2,1], [1,1], [0,1], [0,2]], 
								[[1,0],	[1,1], [1,2], [2,2]],
							],			// J
							[
								[[0,0], [0,1], [1,1], [1,2]], 
								[[0,2], [1,2], [1,1], [2,1]], 
							],			// Z
							[
								[[0,2], [0,1], [1,1], [1,0]], 
								[[2,2], [1,2], [1,1], [0,1]], 
							],			// S
							[
								[[0,1], [1,0], [1,1], [1,2]], 
								[[1,2], [0,1], [1,1], [2,1]], 
								[[2,1], [1,2], [1,1], [1,0]], 
								[[1,0], [2,1], [1,1], [0,1]],
							],			// T
							[
								[[0,1], [1,1], [2,1], [3,1]], 
								[[1,0],	[1,1], [1,2], [1,3]], 
							],			// |
							[
								[[0,1], [0,2], [1,1], [1,2]],
							]				// O
						];
						this.randomBrick();
					},
					
					// 随机产生一个形状，作为下一个形状
					randomBrick() {
						let randomNum = Math.floor(Math.random() * this.brickList.length);
						this.brickNext = this.brickList[randomNum][0]; // 默认拿出某个图形的第一个形态
						this.nextIndex1 = randomNum;
						this.nextIndex2 = 0;
						for (let i = 0; i < 4; i++) {
							for(let j = 0; j < 4; j++) {
								this.list_next[i][j] = 0;
							}
						}
						// 把下一个渲染在next数组上面
						for (let i = 0; i < this.brickNext.length; i++) {
							let [x, y] = this.brickNext[i];
							this.list_next[x][y] = 1;
						}
						this.$forceUpdate()
					},
					
					// 把下一个形状传递给当前
					// 传递的时候需要注意，我们默认的图形是在边缘创建的，我们传递给当前的话，需要把图形放到中间去
					// 传递完，自己要生成一个新的图案
					onPassOn() {
						let arr = JSON.parse(JSON.stringify(this.brickNext));
						let a = Math.floor(this.col / 2 - 2);
						arr.forEach(item => {
							item[1] += a
						})
						this.brickNow = arr;
						this.nowIndex1 = JSON.parse(JSON.stringify(this.nextIndex1));
						this.nowIndex2 = JSON.parse(JSON.stringify(this.nextIndex2));
						this.drawdBrick()
						this.randomBrick()
					},
					
					// 渲染方块
					drawdBrick() {
						for (let i = 0; i < this.list.length; i++) {
							for(let j = 0; j < this.list[i].length; j++) {
								if(this.list[i][j] !== 2) {
									this.list[i][j] = 0;
								}
							}
						}
						for (let i = 0; i < this.brickNow.length; i++) {
							let [x, y] = this.brickNow[i];
							this.list[x][y] = 1;
						}
						this.$forceUpdate();
						this.isEnd();
					},
					
					// 方块下落
					dropBrick() {
						this.timer = setInterval(() => {
							// 如果到底了，则需要把方块固定， 然后进行下一个
							if(this.isToBottom()) {
								let fixedArr = JSON.parse(JSON.stringify(this.brickNow))
								for (let i = 0; i < fixedArr.length; i++) {
									let [x, y] = fixedArr[i];
									this.list[x][y] = 2;
								}
								this.onClean();
								return;
							}
							// 每个砖块的row都加1 = 下落
							for (let i = 0; i < this.brickNow.length; i++) {
								this.brickNow[i][0] += 1;
							}
							this.drawdBrick()
						}, this.speed)
					},
					
					// 上下左右
					onDirection(code) {
						if(!this.isStart || this.isOver) return;
						// 上  旋转
						if(code == 38 || code == 87) {
							this.onRotate()
						}
						// 下  加快下落
						// 找出图案最下面，首先触碰的砖块
						if((code == 40 || code == 83) && !this.isToBottom()) {
							let brickCopy = JSON.parse(JSON.stringify(this.brickNow));
							for(let i = 0; i < this.row; i++) {
								for(let j = 0; j < brickCopy.length; j++) {
									brickCopy[j][0] += 1;
								}
								if(this.isToBottom(brickCopy)) {
									this.brickNow = JSON.parse(JSON.stringify(brickCopy));
									break;
								}
							}
						}
						// 左  左移动
						if((code == 37 || code == 65) && !this.isToLeft()) {
							for (let i = 0; i < this.brickNow.length; i++) {
								this.brickNow[i][1] -= 1;
							}
						}
						// 右  右移动
						if((code == 39 || code == 68) && !this.isToRight()) {
							for (let i = 0; i < this.brickNow.length; i++) {
								this.brickNow[i][1] += 1;
							}
						}
						this.drawdBrick()
					},
					
					// 空格
					onSpace() {
						if(this.isOver) return;
						if(!this.isStart) {
							this.isStart = true;
							this.onPassOn()
						}
						if(this.isStop) {
							this.isStop = false;
							this.dropBrick()
						} else {
							this.isStop = true;
							clearInterval(this.timer)
						}
					},
					
					// 旋转
					// 我们只要计算 原始图形的位置与当前图形位置中每个砖块的row col的差值
					// 再去获取旋转后的原始图形，再把旋转后的原始图形的row col都加上差值
					// 就会得到当前旋转后的图形及位置
					onRotate() {
						let arr = JSON.parse(JSON.stringify(this.brickList[this.nowIndex1]));	// 获取当前图形的变化数组
						if(arr.length > 1) {
							let protoBrick = JSON.parse(JSON.stringify(this.brickList[this.nowIndex1][this.nowIndex2]));
							let brickNowCopy = JSON.parse(JSON.stringify(this.brickNow));
							let gapArr = [];	// 差值列表
							for(let a = 0; a < brickNowCopy.length; a++) {
								gapArr[a] = [
									brickNowCopy[a][0] - protoBrick[a][0], 
									brickNowCopy[a][1] - protoBrick[a][1]
								];
							}
							this.nowIndex2 += 1;
							if(this.nowIndex2 == arr.length) {
								this.nowIndex2 = 0;
							}
							let [xGap, yGap] = gapArr[0];
							let protoBrickNextCopy = JSON.parse(JSON.stringify(this.brickList[this.nowIndex1][this.nowIndex2]));
							protoBrickNextCopy.forEach(item => {
								item[0] += xGap;
								item[1] += yGap;
							})
							// 得到旋转后的图形之后，我们需要判断 图形的左边数组中，是否有负数或者大于地图的边界值（出地图了），
							// 或者坐标映射在地图中是否有等于2的情况（重叠了），这两种情况都不能旋转
							let bool = false;
							for(let a = 0; a < protoBrickNextCopy.length; a++) {
								let [x, y] = protoBrickNextCopy[a];
								if(x < 0 || y < 0 || x > this.row - 1 || y > this.col - 1 || this.list[x][y] == 2) {
									bool = true;
									break;
								}
							}
							// 不能旋转，同时要把当前图形的下标返回之前的那个
							if(bool) {
								this.nowIndex2 -= 1;
								if(this.nowIndex2 == -1) {
									this.nowIndex2 = arr.length - 1;
								}
								return;
							}
							
							this.brickNow = protoBrickNextCopy;
							this.drawdBrick()
						}
					},
					
					// 消除行
					async onClean() {
						let arr = JSON.parse(JSON.stringify(this.list));
						let clearList = [];	// 消除几行
						for(let i = arr.length - 1; i >= 0; i--) {
							let a = arr[i].every(item => item == 2);
							if(a) {
								clearList.push(i);
							}
						}
						if(clearList.length > 0) {
							// 这里要做闪动的效果，用了定时器处理，效果开始之前不能让方块往下滑动，要暂停，
							// 结束才继续滑动
							clearInterval(this.timer);
							for(let z = 0; z < 5; z++) {
								await this.sleep(100)
								for(let i = 0; i < clearList.length; i++) {
									let a = clearList[i];
									for(let j = 0; j < arr[a].length; j++) {
										this.list[a][j] = (z % 2 == 0 ? 0 : 1);
									}
								}
								this.$forceUpdate();
							}
							// 消除行之后 剩余的要下移	
							// 我们要从上往下进行一行一行处理
							for(let i = clearList.length; i >= 0; i--) {
								for(let j = clearList[i] - 1; j >= 0; j--) {
									for(let k = 0; k < this.col; k++) {
										if(this.list[j][k] == 2) {
											this.list[j][k] = 0;
											this.list[j + 1][k] = 2; 
										}
									}
								}
							}
							this.$forceUpdate();
							// 计算分数
							this.onCountScore(clearList.length);
							// 继续下滑
							this.dropBrick()
						}
						// 无论有没有消行，都要把下一个图形拿过来用
						this.onPassOn();
					},
					
					// 计算分数
					onCountScore(num) {
						const arr = this.scoreList.filter(item => item.row == num)[0];
						this.score += arr.score;
					},
					
					// 判断游戏是否结束， 只要第一行有砖块的值 = 2，游戏结束
					isEnd() {
						let bool = false;
						this.list[0].forEach(item => {
							if(item == 2) {
								bool = true;
							}
						});
						if(bool) {
							clearInterval(this.timer);
							this.isOver = true;
							this.isStart = false;
							this.isStop = true;
							this.brickNow = [];
							this.score = 0;
							this.initAnimate()
						}
					},
					
					// 判断是否触底 true到底或者下面已有砖块
					isToBottom(arr = this.brickNow){
						let bool = false;
						arr.forEach(item=>{
							if(item[0] == this.row - 1 || this.list[item[0] + 1][item[1]] == 2){
								bool = true;
							}
						})
						return bool;
					},
					
					// 判断左边是否到边，或者是否有砖块
					isToLeft() {
						let bool = false;
						this.brickNow.forEach(item=>{
							let [x, y] = item;
							if(y == 0 || this.list[x][y - 1] == 2){
								bool = true;
							}
						})
						return bool;
					},
					
					// 判断右边是否有边，或者是否有砖块
					isToRight() {
						let bool = false;
						this.brickNow.forEach(item=>{
							let [x, y] = item;
							if(y == this.col - 1 || this.list[x][y + 1] == 2){
								bool = true;
							}
						})
						return bool;
					},
				
				}
			}).mount('#app');
		</script>
	</body>
</html>
